package com.hockeo.server.rpc.command;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.hockeo.server.rpc.AuthenticationType;
import com.hockeo.shared.VHUtil;
import com.hockeo.shared.rpc.command.ICommand;
import com.hockeo.shared.rpc.command.IResponse;
import com.hockeo.shared.rpc.command.IRpcCommandService;
import com.hockeo.shared.rpc.command.error.CommandException;
import com.hockeo.shared.rpc.command.error.NotAnAdminException;
import com.hockeo.shared.rpc.command.error.NotAuthenticatedException;
import com.hockeo.shared.rpc.command.error.SessionInvalidException;

/**
 * Remote service handling commands issued by a GWT client.
 *
 * @version $Id$
 * @author jjanke
 */
public class RpcCommandServiceImpl extends RemoteServiceServlet implements IRpcCommandService
{
  private static final long serialVersionUID = 1L;

  @SuppressWarnings( "unchecked" )
  @Override
  public IResponse execute( String strClientSessionId, ICommand<?> command ) throws CommandException
  {
    ICommandExecutor<ICommand<IResponse>, IResponse> exec = CommandExecutorLookup.get().getExecutor( command.getClass() );
    CommandExecutionOptions opts = exec.getClass().getAnnotation( CommandExecutionOptions.class );

    // if some kind of access protection is enforced, we force an authentication check
    //
    if ( opts != null )
      checkAuthentication( strClientSessionId, opts.authType() );

    return exec.execute( getContext(), (ICommand<IResponse>) command );
  }

  /**
   * Returns the current command execution context.
   */
  private CommandExecutionContext getContext()
  {
    return new CommandExecutionContext( this, getThreadLocalRequest(), getThreadLocalResponse() );
  }

  /**
   * Checks whether the user issuing the command is authorized to execute it or not.
   *
   * @param strClientSessionId the session Id that was sent with the last request
   * @param authType the type of authentication to enforce
   * @throws CommandException if there is an authentication related problem
   */
  private void checkAuthentication( String strClientSessionId, AuthenticationType authType ) throws CommandException
  {
    if ( authType == null || authType == AuthenticationType.NONE )
      return;

    UserService userService = UserServiceFactory.getUserService();

    if ( !userService.isUserLoggedIn() )
      throw new NotAuthenticatedException( userService.createLoginURL( VHUtil.URL_WELCOME_PAGE ) );

    // some user is logged in, we have to check whether the session id provided by the
    // client corresponds to the one from the request to avoid CSRF attacks
    //
    if ( !isSessionValid( strClientSessionId, getThreadLocalRequest() ) )
      throw new SessionInvalidException( userService.createLoginURL( VHUtil.URL_WELCOME_PAGE ) );

    // check if the authenticated user is an admin (if required)
    //
    if ( authType == AuthenticationType.ADMIN && !userService.isUserAdmin() )
      throw new NotAnAdminException();
  }

  /**
   * Checks if the session id provided by the client corresponds to the one in the
   * request. This method is assumed to be called when a user has been identified as
   * connected.
   *
   * @param strClientSessionId the client session id
   * @param rq the last incoming request
   * @return whether the session is valid or not
   */
  private boolean isSessionValid( String strClientSessionId, HttpServletRequest rq )
  {
    // if we are running locally, let's assume everything is right (to account for GAE dev
    // mode)
    //
    if ( rq.getServerName().equals( "localhost" ) )
      return true;

    // if we have no client session, something is wrong as the user should be identified
    //
    if ( strClientSessionId == null )
      return false;

    String sessionId = "";

    for ( Cookie cookie : rq.getCookies() )
      if ( cookie.getName().equals( VHUtil.SESSION_COOKIE ) )
      {
        sessionId = cookie.getValue();
        break;
      }

    return sessionId.equals( strClientSessionId );
  }
}
